<?php
namespace app\openapi\controller;
use app\model\ApiMsg as ApiMsgModel;
use think\Db;

use app\lib\baidu\Api as baiduApi;
use app\lib\baidu\Api360 as api360;
use app\lib\baidu\Tongyi as TongyiApi;
use app\lib\baidu\Moonshot as Moonshot;



class Msg extends Base
{

    // 获取历史记录
    public function getMessages($session_sn,$model){
        $user_id = $this->login_user['user_id'];
        $map = [
            ['session_sn','=',$session_sn],
            ['user_id','=',$user_id]
        ];
        $msgs = ApiMsgModel::field('message,reply')->where($map)->order('id','desc')->limit(3)->select();
        $messages = [];

        for($i = count($msgs) - 1;$i >= 0;$i--){
            if (empty($msgs[$i]['message']) || empty($msgs[$i]['reply'])) {
                continue;
            }

            if ( in_array($model,['farui']) ) {
                $messages[] = [
                    'user'=>$msgs[$i]['message'],
                    'bot'=>$msgs[$i]['reply']
                ];
            }else{
                $messages[] = [
                    'role'=>'user',
                    'content'=>$msgs[$i]['message']
                ];
                $messages[] = [
                    'role'=>'assistant',
                    'content'=>$msgs[$i]['reply']
                ];
            }
            
        }
        
        return $messages;
    }

  
    
    public function chatapi(){
        set_time_limit ( 0 );
        ini_set('memory_limit','-1');
        ini_set('max_execution_time',0);

        $ulogin = $this->login_user;

        $param = file_get_contents('php://input');
        $param = json_decode($param,true);
        $fields = [
            'must'=>['model','message'],
            'nomust'=>['session_sn','history','system']
        ];
        $params = paramFilter($param,$fields);
        // $params['model'] = 'moonshot-v1-8k';
        // $params['message'] = '你好';

        if (!empty($params['error_msg'])) {
            return apiResult(5000,$params['error_msg']);
        }

        $curtime = time();
        $message = trim($params['message']);
        if (empty($message)) {
            return apiResult('5000','message不能为空');
        }
        $config = ApiMsgModel::getConfig($params['model']);
        if ( empty($config) ) {
            return apiResult('5000','model不存在');
        }
        $config['model'] = $params['model'];

        $balance = Db::name('user')->where('id',$ulogin['user_id'])->value('balance');
        if ($balance <= 0) {
            return apiResult('5000','余额不足');
        }

        // 获取历史消息
        $messages = [];
        if (!empty($params['session_sn'])) {
            $messages = $this->getMessages($params['session_sn'],$params['model']);
        }elseif(!empty($params['history'])){
            $messages = $params['history'];
        }

        if (in_array($params['model'], ['ERNIE-4.0'])) {
            $api = new baiduApi();
            $token = $api->token($config);
            if (empty($token['access_token'])) {
                return apiResult('5000','模型调用失败');
            }
            $messages[] = [
                'role'=>'user',
                'content'=>$message
            ];
            $result = $api->chat($config['api_url'],$token['access_token'],$messages,$params['system']);
        }elseif(in_array($params['model'], ['qwen-turbo','qwen-plus','qwen-max','qwen-max-0403','qwen-max-0107','qwen-max-1201','qwen-max-longcontext','farui-plus'])){
            $api = new TongyiApi();
            $messages[] = [
                'role'=>'user',
                'content'=>$message
            ];
            if (!empty($params['system'])) {
                array_unshift($messages,[
                    'role'=>'system',
                    'content'=>$params['system']
                ]);
            }
            $result = $api->chat($config['api_url'],$config,$messages);
        }elseif(in_array($params['model'], ['farui'])){
            $api = new TongyiApi();
            $body = [
                'prompt'=>$message
            ];
            if (!empty($messages)) {
                $body['history'] = $messages;
            }
            $result = $api->farui($config['api_url'],$body,$config);
        }elseif(in_array($params['model'], ['moonshot-v1-8k','moonshot-v1-32k','moonshot-v1-128k'])){
            $api = new Moonshot();
            $messages[] = [
                'role'=>'user',
                'content'=>$message
            ];
            $result = $api->chat($config['api_url'],$config,$messages);
        }

        if($result['status'] == 0){
            return apiResult('5000',$result['msg']);
        }

        $save_data = [
            'user_id'=>$ulogin['user_id'],
            'session_sn'=>$params['session_sn'],
            'message'=>$message,
            'reply'=>$result['answer'],
            'model'=>$params['model'],
            'ctime'=>$curtime,
            'rtime'=>time()
        ];
        $msg_id = ApiMsgModel::insertGetId($save_data);
        if ( $msg_id > 0 ) {
            // 计费接口
            Db::name('user')->where('id',$ulogin['user_id'])->dec('balance')->update();

        }
        return apiResult('2000','获取成功',$save_data);
    }

    // 全局sse返回内容
    public $sse_reply = '';

    // 输出sse答复内容
    public function sendSseReply(){
        $content = json_encode(['is_end'=>false,'ctime'=>time(),'result'=>$this->sse_reply],JSON_UNESCAPED_UNICODE);
        echo "data:".$content."\n\n";
        //刷新缓冲区
        ob_flush();
        //将输出缓冲区的内容立即发送到客户端
        flush();
    }

    public function chatsse(){
        // 这行代码用于关闭输出缓冲。关闭后，脚本的输出将立即发送到浏览器，而不是等待缓冲区填满或脚本执行完毕。
        ini_set('output_buffering', 'off');
        // 这行代码禁用了 zlib 压缩。通常情况下，启用 zlib 压缩可以减小发送到浏览器的数据量，但对于服务器发送事件来说，实时性更重要，因此需要禁用压缩。
        ini_set('zlib.output_compression', false);
        // 这行代码使用循环来清空所有当前激活的输出缓冲区。ob_end_flush() 函数会刷新并关闭最内层的输出缓冲区，@ 符号用于抑制可能出现的错误或警告。
        while (@ob_end_flush()) {}
        // 这行代码设置 HTTP 响应的 Content-Type 为 text/event-stream，这是服务器发送事件（SSE）的 MIME 类型。
        header('Content-Type: text/event-stream');
        // 这行代码设置 HTTP 响应的 Cache-Control 为 no-cache，告诉浏览器不要缓存此响应。
        header('Cache-Control: no-cache');
        // 这行代码设置 HTTP 响应的 Connection 为 keep-alive，保持长连接，以便服务器可以持续发送事件到客户端。
        header('Connection: keep-alive');
        // 这行代码设置 HTTP 响应的自定义头部 X-Accel-Buffering 为 no，用于禁用某些代理或 Web 服务器（如 Nginx）的缓冲。这有助于确保服务器发送事件在传输过程中不会受到缓冲影响
        header('X-Accel-Buffering: no');


        $param = file_get_contents('php://input');
        $param = json_decode($param,true);
        $fields = [
            'must'=>['model','message'],
            'nomust'=>['session_sn','history','system']
        ];
        $params = paramFilter($param,$fields);
        // $params['model'] = 'moonshot-v1-8k';
        // $params['message'] = '你好';

        if (!empty($params['error_msg'])) {
            // return apiResult(5000,$params['error_msg']);
        }

        $curtime = time();
        $message = trim($params['message']);
        if (empty($message)) {
            return apiResult('5000','message不能为空');
        }
        $config = ApiMsgModel::getConfig($params['model']);
        if ( empty($config) ) {
            return apiResult('5000','model不存在');
        }
        $config['model'] = $params['model'];

        $balance = Db::name('user')->where('id',$ulogin['user_id'])->value('balance');
        if ($balance <= 0) {
            return apiResult('5000','余额不足');
        }

        // 获取历史消息
        $messages = [];
        if (!empty($params['session_sn'])) {
            $messages = $this->getMessages($params['session_sn'],$params['model']);
        }elseif(!empty($params['history'])){
            foreach ($params['history'] as $k => $vo) {
                if (!empty($vo['user']) && !empty($vo['bot'])) {
                    $messages[] = [
                        'role'=>'user',
                        'content'=>$vo['user']
                    ];
                    $messages[] = [
                        'role'=>'assistant',
                        'content'=>$vo['bot']
                    ];
                }
            }
        }

        if (in_array($params['model'], ['ERNIE-4.0'])) {
            $api = new baiduApi();
            $token = $api->token($config);
            if (empty($token['access_token'])) {
                return apiResult('5000','模型调用失败');
            }
            $messages[] = [
                'role'=>'user',
                'content'=>$message
            ];

            $api_url = $config['api_url'].'?access_token='.$token['access_token'];
            $body = [
                "messages" => $messages
            ];
            if (!empty($params['system'])) {
                $body['system'] = $params['system'];
            }
            $this->callBaiduWorkshopSSE($api_url, $body, function ($msg) {
                if ($msg["type"] == "DATA") {
                    $this->sse_reply .= $msg["content"]['result'];
                    $this->sendSseReply();
                } else if ($msg["type"] == "END") {
                    $content = json_encode(['is_end'=>true,'ctime'=>time(),'result'=>$this->sse_reply],JSON_UNESCAPED_UNICODE);
                    echo "data:".$content."\n\n";
                } else {
                    // var_dump($msg);
                }
            });
        }elseif(in_array($params['model'], ['qwen-turbo','qwen-plus','qwen-max','qwen-max-0403','qwen-max-0107','qwen-max-1201','qwen-max-longcontext','farui-plus'])){
            $messages[] = [
                'role'=>'user',
                'content'=>$message
            ];
            if (!empty($params['system'])) {
                array_unshift($messages,[
                    'role'=>'system',
                    'content'=>$params['system']
                ]);
            }
            $body = [
                'model'=>$params['model'],
                'input'=>[
                    'messages'=>$messages
                ]
            ];
           
            $this->callTongyiWorkshopSSE($config['api_url'],$config, $body, function ($msg) {
                if ($msg["type"] == "DATA") {
                    $this->sse_reply = $msg["content"]['output']['text'];
                    $content = json_encode(['is_end'=>false,'ctime'=>time(),'result'=>$this->sse_reply],JSON_UNESCAPED_UNICODE);
                    $this->sendSseReply();
                } else if ($msg["type"] == "END") {
                    $content = json_encode(['is_end'=>true,'ctime'=>time(),'result'=>$this->sse_reply],JSON_UNESCAPED_UNICODE);
                    echo "data:".$content."\n\n";
                } else {
                    // var_dump($msg);
                }
            });
        }elseif(in_array($params['model'], ['farui'])){
            $api = new TongyiApi();
            $body = [
                'prompt'=>$message
            ];
            if (!empty($messages)) {
                $body['history'] = $messages;
            }
            $config['uuid'] = uuidCreate();
            
            do {
                $result = $api->faruiSse($config['api_url'],$body,$config);
                if ($result['status'] == 1) {
                    $this->sse_reply = $result['answer'];
                    if ($result['is_finish'] == 0) {
                        $content = json_encode(['is_end'=>false,'ctime'=>time(),'result'=>$this->sse_reply],JSON_UNESCAPED_UNICODE);
                        $this->sendSseReply();

                        sleep(1);
                    }else{
                        $content = json_encode(['is_end'=>true,'ctime'=>time(),'result'=>$this->sse_reply],JSON_UNESCAPED_UNICODE);
                        echo "data:".$content."\n\n";
                    }
                }
            } while ($result['status']==1 && $result['is_finish'] == 0);
        }elseif(in_array($params['model'], ['moonshot-v1-8k','moonshot-v1-32k','moonshot-v1-128k'])){
            $messages[] = [
                'role'=>'user',
                'content'=>$message
            ];
            $body = [
                'model'=>$params['model'],
                'messages'=>$messages,
                'stream'=>true
            ];
           
            $this->callMoonshotWorkshopSSE($config['api_url'],$config, $body, function ($msg) {
                if ($msg["type"] == "DATA") {
                    $see_content = $msg["content"]['choices'][0]['delta']['content'];
                    if (!empty($see_content)) {
                        $this->sse_reply .= $see_content;
                        $content = json_encode(['is_end'=>false,'ctime'=>time(),'result'=>$this->sse_reply],JSON_UNESCAPED_UNICODE);
                        $this->sendSseReply();
                    }
                } else if ($msg["type"] == "END") {
                    $content = json_encode(['is_end'=>true,'ctime'=>time(),'result'=>$this->sse_reply],JSON_UNESCAPED_UNICODE);
                    echo "data:".$content."\n\n";
                } else {
                    // var_dump($msg);
                }
            });
        }

        if (!empty($this->sse_reply)) {
            $ulogin = $this->login_user;
            $save_data = [
                'user_id'=>$ulogin['user_id'],
                'session_sn'=>$params['session_sn'],
                'message'=>$message,
                'reply'=>$this->sse_reply,
                'model'=>$params['model'],
                'ctime'=>$curtime,
                'rtime'=>time()
            ];
            $msg_id = ApiMsgModel::insertGetId($save_data);
            if ( $msg_id > 0 ) {
                // 计费接口
                Db::name('user')->where('id',$ulogin['user_id'])->dec('balance')->update();
            }
        }
    }

    public function callMoonshotWorkshopSSE($url,$api_param, $body, $onMessage) {
        return (new class() {
            private $buffer = "";
            private $dataMsgBuffer = "";

            public function call($url,$api_param, $body, $onMessage)
            {
                // $body['stream'] = true;
                $curl = curl_init();
                curl_setopt_array($curl, array(
                    CURLOPT_URL => $url,
                    CURLOPT_RETURNTRANSFER => true,
                    CURLOPT_ENCODING => '',
                    CURLOPT_MAXREDIRS => 10,
                    CURLOPT_TIMEOUT => 0,
                    CURLOPT_FOLLOWLOCATION => true,
                    CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
                    CURLOPT_CUSTOMREQUEST => 'POST',
                    CURLOPT_POSTFIELDS => json_encode($body),
                    CURLOPT_HTTPHEADER => array(
                        'Content-Type: application/json',
                        'Authorization:Bearer '.$api_param['key']

                    ),
                    CURLOPT_WRITEFUNCTION => function ($curl, $str) use ($onMessage) {
                        $this->buffer = $this->buffer . $str;
                        $lines = explode("\n", $this->buffer);
                        $this->buffer = array_pop($lines);
                        foreach ($lines as $line) {
                            if ($line == "") { //读取到空行，一个数据块发送完成
                                $onMessage([
                                    "type" => "DATA",
                                    "content" => json_decode($this->dataMsgBuffer,true)
                                ]);
                                $this->dataMsgBuffer = "";
                                continue;
                            }
                            $msgPart = explode(":", $line, 2);
                            $type = $msgPart[0];
                            $content = $msgPart[1];
                            if ($type == 'data') { //数据块没有收到空行之前放入buffer中
                                $this->dataMsgBuffer = $this->dataMsgBuffer . trim($content);
                            } else if ($type == '' && $content != '') { //服务端发送的注释，用于保证链接不断开
                                $onMessage([
                                    "type" => "COMMENT",
                                    "content" => trim($content)
                                ]);
                            } else {
                                $onMessage([
                                    "type" => $type,
                                    "content" => trim($content)
                                ]);
                            }
                        }
                        return strlen($str);
                    }
                ));
                curl_exec($curl);
                if (curl_errno($curl)) {
                    $onMessage([
                        "type" => "ERROR",
                        "content" => curl_strerror(curl_errno($curl))
                    ]);
                } else {
                    $onMessage([
                        "type" => "END"
                    ]);
                }
                curl_close($curl);
            }
        })->call($url,$api_param, $body, $onMessage);
    }

    public function callTongyiWorkshopSSE($url,$api_param, $body, $onMessage) {
        return (new class() {
            private $buffer = "";
            private $dataMsgBuffer = "";

            public function call($url,$api_param, $body, $onMessage)
            {
                // $body['stream'] = true;
                $curl = curl_init();
                curl_setopt_array($curl, array(
                    CURLOPT_URL => $url,
                    CURLOPT_RETURNTRANSFER => true,
                    CURLOPT_ENCODING => '',
                    CURLOPT_MAXREDIRS => 10,
                    CURLOPT_TIMEOUT => 0,
                    CURLOPT_FOLLOWLOCATION => true,
                    CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
                    CURLOPT_CUSTOMREQUEST => 'POST',
                    CURLOPT_POSTFIELDS => json_encode($body),
                    CURLOPT_HTTPHEADER => array(
                        'Content-Type: application/json',
                        'Authorization:Bearer '.$api_param['api_key'],
                        'Accept:text/event-stream'

                    ),
                    CURLOPT_WRITEFUNCTION => function ($curl, $str) use ($onMessage) {
                        $this->buffer = $this->buffer . $str;
                        $lines = explode("\n", $this->buffer);
                        $this->buffer = array_pop($lines);
                        foreach ($lines as $line) {
                            if ($line == "") { //读取到空行，一个数据块发送完成
                                $onMessage([
                                    "type" => "DATA",
                                    "content" => json_decode($this->dataMsgBuffer,true)
                                ]);
                                $this->dataMsgBuffer = "";
                                continue;
                            }
                            $msgPart = explode(":", $line, 2);
                            $type = $msgPart[0];
                            $content = $msgPart[1];
                            if ($type == 'data') { //数据块没有收到空行之前放入buffer中
                                $this->dataMsgBuffer = $this->dataMsgBuffer . trim($content);
                            } else if ($type == '' && $content != '') { //服务端发送的注释，用于保证链接不断开
                                $onMessage([
                                    "type" => "COMMENT",
                                    "content" => trim($content)
                                ]);
                            } else {
                                $onMessage([
                                    "type" => $type,
                                    "content" => trim($content)
                                ]);
                            }
                        }
                        return strlen($str);
                    }
                ));
                curl_exec($curl);
                if (curl_errno($curl)) {
                    $onMessage([
                        "type" => "ERROR",
                        "content" => curl_strerror(curl_errno($curl))
                    ]);
                } else {
                    $onMessage([
                        "type" => "END"
                    ]);
                }
                curl_close($curl);
            }
        })->call($url,$api_param, $body, $onMessage);
    }

    public function callBaiduWorkshopSSE($url, $body, $onMessage) {
        return (new class() {
            private $buffer = "";
            private $dataMsgBuffer = "";

            public function call($url, $body, $onMessage)
            {
                $body['stream'] = true;
                $curl = curl_init();
                curl_setopt_array($curl, array(
                    CURLOPT_URL => $url,
                    CURLOPT_RETURNTRANSFER => true,
                    CURLOPT_ENCODING => '',
                    CURLOPT_MAXREDIRS => 10,
                    CURLOPT_TIMEOUT => 0,
                    CURLOPT_FOLLOWLOCATION => true,
                    CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
                    CURLOPT_CUSTOMREQUEST => 'POST',
                    CURLOPT_POSTFIELDS => json_encode($body),
                    CURLOPT_HTTPHEADER => array(
                        'Content-Type: application/json'
                    ),
                    CURLOPT_WRITEFUNCTION => function ($curl, $str) use ($onMessage) {
                        $this->buffer = $this->buffer . $str;
                        $lines = explode("\n", $this->buffer);
                        $this->buffer = array_pop($lines);
                        foreach ($lines as $line) {
                            if ($line == "") { //读取到空行，一个数据块发送完成
                                $onMessage([
                                    "type" => "DATA",
                                    "content" => json_decode($this->dataMsgBuffer,true)
                                ]);
                                $this->dataMsgBuffer = "";
                                continue;
                            }
                            $msgPart = explode(":", $line, 2);
                            $type = $msgPart[0];
                            $content = $msgPart[1];
                            if ($type == 'data') { //数据块没有收到空行之前放入buffer中
                                $this->dataMsgBuffer = $this->dataMsgBuffer . trim($content);
                            } else if ($type == '' && $content != '') { //服务端发送的注释，用于保证链接不断开
                                $onMessage([
                                    "type" => "COMMENT",
                                    "content" => trim($content)
                                ]);
                            } else {
                                $onMessage([
                                    "type" => $type,
                                    "content" => trim($content)
                                ]);
                            }
                        }
                        return strlen($str);
                    }
                ));
                curl_exec($curl);
                if (curl_errno($curl)) {
                    $onMessage([
                        "type" => "ERROR",
                        "content" => curl_strerror(curl_errno($curl))
                    ]);
                } else {
                    $onMessage([
                        "type" => "END"
                    ]);
                }
                curl_close($curl);
            }
        })->call($url, $body, $onMessage);
    }

    


   
}
